import copy
import warnings
from collections import OrderedDict, namedtuple

from mongoengine import fields as me_fields
from mongoengine.errors import ValidationError as me_ValidationError
from rest_framework import fields as drf_fields
from rest_framework import serializers
from rest_framework.serializers import ALL_FIELDS
from rest_framework.utils.field_mapping import ClassLookupDict

from rest_framework_mongoengine import fields as drfm_fields
from rest_framework_mongoengine.validators import (
    UniqueTogetherValidator, UniqueValidator
)

from .repr import serializer_repr
from .utils import (
    COMPOUND_FIELD_TYPES, get_field_info, get_field_kwargs,
    get_generic_embedded_kwargs, get_nested_embedded_kwargs,
    get_nested_relation_kwargs, get_relation_kwargs, has_default,
    is_abstract_model
)

# This object is used for customization of nested field attributes in DocumentSerializer
Customization = namedtuple("Customization", [
    'fields',
    'exclude',
    'extra_kwargs',
    'validate_methods'
])


def raise_errors_on_nested_writes(method_name, serializer, validated_data):
    # *** inherited from DRF 3, altered for EmbeddedDocumentSerializer to pass ***
    assert not any(
        isinstance(field, serializers.BaseSerializer) and
        not isinstance(field, EmbeddedDocumentSerializer) and
        (key in validated_data)
        for key, field in serializer.fields.items()
    ), (
        'The `.{method_name}()` method does not support writable nested'
        'fields by default.\nWrite an explicit `.{method_name}()` method for '
        'serializer `{module}.{class_name}`, or set `read_only=True` on '
        'nested serializer fields.'.format(
            method_name=method_name,
            module=serializer.__class__.__module__,
            class_name=serializer.__class__.__name__
        )
    )

    assert not any(
        '.' in field.source and (key in validated_data) and
        isinstance(validated_data[key], (list, dict))
        for key, field in serializer.fields.items()
    ), (
        'The `.{method_name}()` method does not support writable dotted-source '
        'fields by default.\nWrite an explicit `.{method_name}()` method for '
        'serializer `{module}.{class_name}`, or set `read_only=True` on '
        'dotted-source serializer fields.'.format(
            method_name=method_name,
            module=serializer.__class__.__module__,
            class_name=serializer.__class__.__name__
        )
    )


class DocumentSerializer(serializers.ModelSerializer):
    """ Serializer for Documents.

    Recognized primitve fields:

        * ``StringField``
        * ``URLField``
        * ``EmailField``
        * ``IntField``
        * ``LongField``
        * ``FloatField``
        * ``DecimalField``
        * ``BooleanField``
        * ``DateTimeField``
        * ``ComplexDateTimeField``
        * ``ObjectIdField``
        * ``SequenceField`` (assumes it has integer counter)
        * ``UUIDField``
        * ``GeoPointField``
        * ``GeoJsonBaseField`` (all those fields)
        * ``DateField``

    Compound fields: ``ListField`` and ``DictField`` are mapped to corresponding DRF fields, with respect to nested field specification.

    The ``ReferenceField`` is handled like ``ForeignKey`` in DRF: there nested serializer autogenerated if serializer depth greater then 0, otherwise it's handled by it's own (results as ``str(id)``).

    For ``EmbeddedDocumentField`` also nested serializer autogenerated for non-zero depth, otherwise it is skipped. TODO: THIS IS PROBABLY WRONG AND SHOULD BE FIXED.

    Generic fields ``GenericReferenceField`` and ``GenericEmbeddedDocumentField`` are handled by their own with corresponding serializer fields.

    Not well supported or untested:

        ``FileField``
        ``ImageField``
        ``BinaryField``

    All other fields are mapped to ``DocumentField`` and probably will work wrong.
    """

    serializer_field_mapping = {
        me_fields.StringField: drf_fields.CharField,
        me_fields.URLField: drf_fields.URLField,
        me_fields.EmailField: drf_fields.EmailField,
        me_fields.IntField: drf_fields.IntegerField,
        me_fields.LongField: drf_fields.IntegerField,
        me_fields.FloatField: drf_fields.FloatField,
        me_fields.DecimalField: drf_fields.DecimalField,
        me_fields.BooleanField: drf_fields.BooleanField,
        me_fields.DateTimeField: drf_fields.DateTimeField,
        me_fields.DateField: drf_fields.DateField,
        me_fields.ComplexDateTimeField: drf_fields.DateTimeField,
        me_fields.ObjectIdField: drfm_fields.ObjectIdField,
        me_fields.FileField: drfm_fields.FileField,
        me_fields.ImageField: drfm_fields.ImageField,
        me_fields.SequenceField: drf_fields.IntegerField,
        me_fields.UUIDField: drf_fields.UUIDField,
        me_fields.GeoPointField: drfm_fields.GeoPointField,
        me_fields.GeoJsonBaseField: drfm_fields.GeoJSONField,
        me_fields.DynamicField: drfm_fields.DynamicField,
        me_fields.BaseField: drfm_fields.DocumentField
    }

    # induct failure if they occasionally used somewhere
    serializer_related_field = None
    serializer_related_to_field = None
    serializer_url_field = None

    " class to create fields for references "
    serializer_reference_base_field = drfm_fields.ReferenceField

    " class to create fields for generic references "
    serializer_reference_generic = drfm_fields.GenericReferenceField

    " class to create nested serializers for references (defaults to DocumentSerializer) "
    serializer_reference_nested = None

    " class to create fields for generic embedded "
    serializer_embedded_generic = drfm_fields.GenericEmbeddedDocumentField

    " class to create nested serializers for embedded (defaults to EmbeddedDocumentSerializer) "
    serializer_embedded_nested = None

    " class to create nested serializers for embedded at max recursion "
    serializer_embedded_bottom = drf_fields.HiddenField

    _saving_instances = True

    def create(self, validated_data):
        raise_errors_on_nested_writes('create', self, validated_data)

        ModelClass = self.Meta.model
        try:
            # recursively create EmbeddedDocuments from their validated data
            # before creating the document instance itself
            instance = self.recursive_save(validated_data)
        except TypeError as exc:
            msg = (
                'Got a `TypeError` when calling `%s.objects.create()`. '
                'This may be because you have a writable field on the '
                'serializer class that is not a valid argument to '
                '`%s.objects.create()`. You may need to make the field '
                'read-only, or override the %s.create() method to handle '
                'this correctly.\nOriginal exception text was: %s.' %
                (
                    ModelClass.__name__,
                    ModelClass.__name__,
                    type(self).__name__,
                    exc
                )
            )
            raise TypeError(msg)
        except me_ValidationError as exc:
            msg = (
                'Got a `ValidationError` when calling `%s.objects.create()`. '
                'This may be because request data satisfies serializer validations '
                'but not Mongoengine`s. You may need to check consistency between '
                '%s and %s.\nIf that is not the case, please open a ticket '
                'regarding this issue on https://github.com/umutbozkurt/django-rest-framework-mongoengine/issues'
                '\nOriginal exception was: %s' %
                (
                    ModelClass.__name__,
                    ModelClass.__name__,
                    type(self).__name__,
                    exc
                )
            )
            raise me_ValidationError(msg)

        return instance

    def update(self, instance, validated_data):
        raise_errors_on_nested_writes('update', self, validated_data)

        instance = self.recursive_save(validated_data, instance)

        return instance

    def recursive_save(self, validated_data, instance=None):
        """
        Recursively traverses validated_data and creates EmbeddedDocuments
        of the appropriate subtype from them.

        Returns Mongonengine model instance.
        """
        # me_data is an analogue of validated_data, but contains
        # mongoengine EmbeddedDocument instances for nested data structures
        # instead of OrderedDicts.
        #
        # For example:
        # validated_data = {'id:, "1", 'embed': OrderedDict({'a': 'b'})}
        # me_data = {'id': "1", 'embed': <EmbeddedDocument>}
        me_data = dict()

        for key, value in validated_data.items():
            try:
                field = self.fields[key]

                # for EmbeddedDocumentSerializers, call recursive_save
                if isinstance(field, EmbeddedDocumentSerializer):
                    me_data[key] = field.recursive_save(value) if value is not None else value # issue when the value is none 

                # same for lists of EmbeddedDocumentSerializers i.e.
                # ListField(EmbeddedDocumentField) or EmbeddedDocumentListField
                elif ((isinstance(field, serializers.ListSerializer) or
                       isinstance(field, serializers.ListField)) and
                      isinstance(field.child, EmbeddedDocumentSerializer)):
                    me_data[key] = []
                    for datum in value:
                        me_data[key].append(field.child.recursive_save(datum))

                # same for dicts of EmbeddedDocumentSerializers (or, speaking
                # in Mongoengine terms, MapField(EmbeddedDocument(Embed))
                elif (isinstance(field, drfm_fields.DictField) and
                      hasattr(field, "child") and
                      isinstance(field.child, EmbeddedDocumentSerializer)):
                    me_data[key] = {}
                    for datum_key, datum_value in value.items():
                        me_data[key][datum_key] = field.child.recursive_save(datum_value)

                # for regular fields just set value
                else:
                    me_data[key] = value

            except KeyError:  # this is dynamic data
                me_data[key] = value

        # create (if needed), save (if needed) and return mongoengine instance
        if not instance:
            instance = self.Meta.model(**me_data)
        else:
            for key, value in me_data.items():
                setattr(instance, key, value)

        if self._saving_instances:
            instance.save()

        return instance

    def to_internal_value(self, data):
        """
        Calls super() from DRF, but with an addition.

        Creates initial_data and _validated_data for nested
        EmbeddedDocumentSerializers, so that recursive_save could make
        use of them.

        If meets any arbitrary data, not expected by fields,
        just silently drops them from validated_data.
        """
        # for EmbeddedDocumentSerializers create initial data
        # so that _get_dynamic_data could use them
        for field in self._writable_fields:
            if isinstance(field, EmbeddedDocumentSerializer) and field.field_name in data:
                field.initial_data = data[field.field_name]

        ret = super(DocumentSerializer, self).to_internal_value(data)

        # for EmbeddedDocumentSerializers create _validated_data
        # so that create()/update() could use them
        for field in self._writable_fields:
            if isinstance(field, EmbeddedDocumentSerializer) and field.field_name in ret:
                field._validated_data = ret[field.field_name]

        return ret

    def get_model(self):
        return self.Meta.model

    def get_fields(self):
        assert hasattr(self, 'Meta'), (
            'Class {serializer_class} missing "Meta" attribute'.format(
                serializer_class=self.__class__.__name__
            )
        )
        assert hasattr(self.Meta, 'model'), (
            'Class {serializer_class} missing "Meta.model" attribute'.format(
                serializer_class=self.__class__.__name__
            )
        )
        depth = getattr(self.Meta, 'depth', 0)
        depth_embedding = getattr(self.Meta, 'depth_embedding', 5)

        if depth is not None:
            assert depth >= 0, "'depth' may not be negative."
            assert depth <= 10, "'depth' may not be greater than 10."

        declared_fields = copy.deepcopy(self._declared_fields)
        model = self.get_model()

        if model is None:
            return {}

        if is_abstract_model(model):
            raise ValueError(
                'Cannot use ModelSerializer with Abstract Models.'
            )

        # Retrieve metadata about fields & relationships on the model class.
        self.field_info = get_field_info(model)
        field_names = self.get_field_names(declared_fields, self.field_info)
        # Determine any extra field arguments and hidden fields that
        # should be included
        extra_kwargs = self.get_extra_kwargs()
        extra_kwargs, hidden_fields = self.get_uniqueness_extra_kwargs(field_names, extra_kwargs)

        # Determine the fields that should be included on the serializer.
        fields = OrderedDict()

        for field_name in field_names:
            # If the field is explicitly declared on the class then use that.
            if field_name in declared_fields:
                fields[field_name] = declared_fields[field_name]
                # We assume that in this case no extra_kwargs etc. should be considered
                # No nested validators or validate_*() methods need to be applied
                continue

            # Determine the serializer field class and keyword arguments.
            field_class, field_kwargs = self.build_field(
                field_name, self.field_info, model, depth, depth_embedding
            )

            extra_field_kwargs = extra_kwargs.get(field_name, {})
            field_kwargs = self.include_extra_kwargs(
                field_kwargs, extra_field_kwargs
            )

            # Create the serializer field.
            fields[field_name] = field_class(**field_kwargs)

        # Add in any hidden fields.
        fields.update(hidden_fields)

        return fields

    def get_field_names(self, declared_fields, info):
        """
        Returns the list of all field names that should be created when
        instantiating this serializer class. This is based on the default
        set of fields, but also takes into account the `Meta.fields` or
        `Meta.exclude` options if they have been specified.

        It includes only direct children of serializer, not its grandchildren.
        """
        fields = getattr(self.Meta, 'fields', None)
        exclude = getattr(self.Meta, 'exclude', None)

        if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
            raise TypeError(
                'The `fields` option must be a list or tuple or "__all__". '
                'Got %s.' % type(fields).__name__
            )

        if exclude and not isinstance(exclude, (list, tuple)):
            raise TypeError(
                'The `exclude` option must be a list or tuple. Got %s.' %
                type(exclude).__name__
            )

        assert not (fields and exclude), (
            "Cannot set both 'fields' and 'exclude' options on "
            "serializer {serializer_class}.".format(
                serializer_class=self.__class__.__name__
            )
        )

        if fields is None and exclude is None:
            warnings.warn(
                "Creating a ModelSerializer without either the 'fields' "
                "attribute or the 'exclude' attribute is deprecated "
                "since 3.3.0. Add an explicit fields = '__all__' to the "
                "{serializer_class} serializer.".format(
                    serializer_class=self.__class__.__name__
                ),
                DeprecationWarning
            )

        if fields == ALL_FIELDS:
            fields = self.get_default_field_names(declared_fields, info)
        else:
            if fields is not None:
                # Ensure that all declared fields have also been included in the
                # `Meta.fields` option.

                # Do not require any fields that are declared a parent class,
                # in order to allow serializer subclasses to only include
                # a subset of fields.
                required_field_names = set(declared_fields)
                for cls in self.__class__.__bases__:
                    required_field_names -= set(getattr(cls, '_declared_fields', []))

                for field_name in required_field_names:
                    assert field_name in fields, (
                        "The field '{field_name}' was declared on serializer "
                        "{serializer_class}, but has not been included in the "
                        "'fields' option.".format(
                            field_name=field_name,
                            serializer_class=self.__class__.__name__
                        )
                    )
            else:
                # Use the default set of field names if `Meta.fields` is not specified.
                fields = self.get_default_field_names(declared_fields, info)

                if exclude is not None:
                    # If `Meta.exclude` is included, then remove those fields.
                    for field_name in exclude:
                        if '.' not in field_name:  # ignore customization of nested fields - they'll be handled separately
                            assert field_name in fields, (
                                "The field '{field_name}' was included on serializer "
                                "{serializer_class} in the 'exclude' option, but does "
                                "not match any model field.".format(
                                    field_name=field_name,
                                    serializer_class=self.__class__.__name__
                                )
                            )
                            fields.remove(field_name)

        # filter out child fields
        return [field_name for field_name in fields if '.' not in field_name]

    def get_default_field_names(self, declared_fields, model_info):
        return (
            [model_info.pk.name] +
            list(declared_fields.keys()) +
            list(model_info.fields.keys()) +
            list(model_info.references.keys()) +
            list(model_info.embedded.keys())
        )

    def get_customization_for_nested_field(self, field_name):
        """ Support of nested fields customization for:
         * EmbeddedDocumentField
         * NestedReference
         * Compound fields with EmbeddedDocument as a child:
            * ListField(EmbeddedDocument)/EmbeddedDocumentListField
            * MapField(EmbeddedDocument)

        Extracts fields, exclude, extra_kwargs and validate_*()
        attributes from parent serializer, related to attributes of field_name.
        """

        # This method is supposed to be called after self.get_fields(),
        # thus it assumes that fields and exclude are mutually exclusive
        # and at least one of them is set.
        #
        # Also, all the sanity checks are left up to nested field's
        # get_fields() method, so if something is wrong with customization
        # nested get_fields() will report this.

        fields = getattr(self.Meta, 'fields', None)
        exclude = getattr(self.Meta, 'exclude', None)

        if fields and fields != ALL_FIELDS and not isinstance(fields, (list, tuple)):
            raise TypeError(
                'The `fields` option must be a list or tuple or "__all__". '
                'Got %s.' % type(fields).__name__
            )

        if exclude and not isinstance(exclude, (list, tuple)):
            raise TypeError(
                'The `exclude` option must be a list or tuple. Got %s.' %
                type(exclude).__name__
            )

        assert not (fields and exclude), (
            "Cannot set both 'fields' and 'exclude' options on "
            "serializer {serializer_class}.".format(
                serializer_class=self.__class__.__name__
            )
        )

        if fields is None and exclude is None:
            warnings.warn(
                "Creating a ModelSerializer without either the 'fields' "
                "attribute or the 'exclude' attribute is deprecated "
                "since 3.3.0. Add an explicit fields = '__all__' to the "
                "{serializer_class} serializer.".format(
                    serializer_class=self.__class__.__name__
                ),
                DeprecationWarning
            )
            fields = ALL_FIELDS  # assume that fields are ALL_FIELDS

        # TODO: validators

        # get nested_fields or nested_exclude (supposed to be mutually exclusive, assign the other one to None)
        if fields:
            if fields == ALL_FIELDS:
                nested_fields = ALL_FIELDS
            else:
                nested_fields = [field[len(field_name + '.'):] for field in fields if field.startswith(field_name + '.')]
            nested_exclude = None
        else:
            # leave all the sanity checks up to get_fields() method of nested field's serializer
            nested_fields = None
            nested_exclude = [field[len(field_name + '.'):] for field in exclude if field.startswith(field_name + '.')]

        # get nested_extra_kwargs (including read-only fields)
        # TODO: uniqueness extra kwargs
        extra_kwargs = self.get_extra_kwargs()
        nested_extra_kwargs = {key[len(field_name + '.'):]: value for key, value in extra_kwargs.items() if key.startswith(field_name + '.')}

        # get nested_validate_methods dict {name: function}, rename e.g. 'validate_author__age()' -> 'validate_age()'
        # so that we can add them to nested serializer's definition under this new name
        # validate_methods are normally checked in rest_framework.Serializer.to_internal_value()
        nested_validate_methods = {}
        for attr in dir(self.__class__):
            if attr.startswith('validate_%s__' % field_name.replace('.', '__')):
                method = getattr(self.__class__, attr)
                method_name = 'validate_' + attr[len('validate_%s__' % field_name.replace('.', '__')):]
                nested_validate_methods[method_name] = method

        return Customization(nested_fields, nested_exclude, nested_extra_kwargs, nested_validate_methods)

    def apply_customization(self, serializer, customization):
        """
        Applies fields customization to a nested or embedded DocumentSerializer.
        """
        # apply fields or exclude
        if customization.fields is not None:
            if len(customization.fields) == 0:
                # customization fields are empty, set Meta.fields to '__all__'
                serializer.Meta.fields = ALL_FIELDS
            else:
                serializer.Meta.fields = customization.fields
        if customization.exclude is not None:
            serializer.Meta.exclude = customization.exclude

        # apply extra_kwargs
        if customization.extra_kwargs is not None:
            serializer.Meta.extra_kwargs = customization.extra_kwargs

        # apply validate_methods
        for method_name, method in customization.validate_methods.items():
            setattr(serializer, method_name, method)

    def build_field(self, field_name, info, model_class, nested_depth, embedded_depth):
        if field_name in info.fields_and_pk:
            model_field = info.fields_and_pk[field_name]
            if isinstance(model_field, COMPOUND_FIELD_TYPES):
                child_name = field_name + '.child'
                if child_name in info.fields or child_name in info.embedded or child_name in info.references:
                    child_class, child_kwargs = self.build_field(child_name, info, model_class, nested_depth, embedded_depth)
                    child_field = child_class(**child_kwargs)
                else:
                    child_field = None
                return self.build_compound_field(field_name, model_field, child_field)
            else:
                return self.build_standard_field(field_name, model_field)

        if field_name in info.references:
            relation_info = info.references[field_name]
            if nested_depth and relation_info.related_model:
                return self.build_nested_reference_field(field_name, relation_info, nested_depth)
            else:
                return self.build_reference_field(field_name, relation_info, nested_depth)

        if field_name in info.embedded:
            relation_info = info.embedded[field_name]
            if not relation_info.related_model:
                return self.build_generic_embedded_field(field_name, relation_info, embedded_depth)
            if embedded_depth:
                return self.build_nested_embedded_field(field_name, relation_info, embedded_depth)
            else:
                return self.build_bottom_embedded_field(field_name, relation_info, embedded_depth)

        if hasattr(model_class, field_name):
            return self.build_property_field(field_name, model_class)

        return self.build_unknown_field(field_name, model_class)

    def build_standard_field(self, field_name, model_field):
        field_mapping = ClassLookupDict(self.serializer_field_mapping)

        field_class = field_mapping[model_field]
        field_kwargs = get_field_kwargs(field_name, model_field)

        if 'choices' in field_kwargs:
            # Fields with choices get coerced into `ChoiceField`
            # instead of using their regular typed field.
            field_class = self.serializer_choice_field
            # Some model fields may introduce kwargs that would not be valid
            # for the choice field. We need to strip these out.
            # Eg. models.DecimalField(max_digits=3, decimal_places=1, choices=DECIMAL_CHOICES)
            valid_kwargs = set((
                'read_only', 'write_only',
                'required', 'default', 'initial', 'source',
                'label', 'help_text', 'style',
                'error_messages', 'validators', 'allow_null', 'allow_blank',
                'choices'
            ))
            for key in list(field_kwargs.keys()):
                if key not in valid_kwargs:
                    field_kwargs.pop(key)

        if 'regex' in field_kwargs:
            field_class = drf_fields.RegexField

        if not issubclass(field_class, drfm_fields.DocumentField):
            # `model_field` is only valid for the fallback case of
            # `ModelField`, which is used when no other typed field
            # matched to the model field.
            field_kwargs.pop('model_field', None)

        if not issubclass(field_class, drf_fields.CharField) and not issubclass(field_class, drf_fields.ChoiceField):
            # `allow_blank` is only valid for textual fields.
            field_kwargs.pop('allow_blank', None)

        if field_class is drf_fields.BooleanField and field_kwargs.get('allow_null', False):
            field_kwargs.pop('allow_null', None)
            field_kwargs.pop('default', None)
            field_class = drf_fields.NullBooleanField

        return field_class, field_kwargs

    def build_compound_field(self, field_name, model_field, child_field):
        if isinstance(model_field, me_fields.ListField):
            field_class = drf_fields.ListField
        elif isinstance(model_field, me_fields.DictField):
            field_class = drfm_fields.DictField
        else:
            return self.build_unknown_field(field_name, model_field.owner_document)

        field_kwargs = get_field_kwargs(field_name, model_field)
        field_kwargs.pop('model_field', None)

        if child_field is not None:
            field_kwargs['child'] = child_field

        return field_class, field_kwargs

    def serializer_reference_field_factory(self, field_class):
        return type(
            self.serializer_reference_base_field.__name__,
            (self.serializer_reference_base_field,),
            {'pk_field_class': field_class}
        )

    def build_reference_field(self, field_name, relation_info, nested_depth):
        if not relation_info.related_model:
            field_class = self.serializer_reference_generic
            field_kwargs = get_relation_kwargs(field_name, relation_info)
            if not issubclass(field_class, drfm_fields.DocumentField):
                field_kwargs.pop('model_field', None)
        else:
            _, related_model = relation_info
            related_class = related_model.id.__class__
            try:
                related_field = self.serializer_field_mapping[related_class]
            except KeyError:
                raise KeyError("Referencing a model with a " +
                               related_class.__name__ +
                               " primary key is not supported")
            field_class = self.serializer_reference_field_factory(related_field)
            field_kwargs = get_relation_kwargs(field_name, relation_info)

        return field_class, field_kwargs

    def build_nested_reference_field(self, field_name, relation_info, nested_depth):
        subclass = self.serializer_reference_nested or DocumentSerializer

        class NestedSerializer(subclass):
            class Meta:
                model = relation_info.related_model
                depth = nested_depth - 1

        # Apply customization to nested fields
        customization = self.get_customization_for_nested_field(field_name)
        self.apply_customization(NestedSerializer, customization)

        field_class = NestedSerializer
        field_kwargs = get_nested_relation_kwargs(field_name, relation_info)
        return field_class, field_kwargs

    def build_generic_embedded_field(self, field_name, relation_info, embedded_depth):
        field_class = self.serializer_embedded_generic
        field_kwargs = get_generic_embedded_kwargs(field_name, relation_info)
        return field_class, field_kwargs

    def build_nested_embedded_field(self, field_name, relation_info, embedded_depth):
        subclass = self.serializer_embedded_nested or EmbeddedDocumentSerializer

        class EmbeddedSerializer(subclass):
            class Meta:
                model = relation_info.related_model
                depth_embedding = embedded_depth - 1

        # Apply customization to nested fields
        customization = self.get_customization_for_nested_field(field_name)
        self.apply_customization(EmbeddedSerializer, customization)

        field_class = EmbeddedSerializer
        field_kwargs = get_nested_embedded_kwargs(field_name, relation_info)
        return field_class, field_kwargs

    def build_bottom_embedded_field(self, field_name, relation_info, embedded_depth):
        field_class = self.serializer_embedded_bottom
        field_kwargs = get_nested_embedded_kwargs(field_name, relation_info)
        field_kwargs['default'] = None
        return field_class, field_kwargs

    def get_uniqueness_extra_kwargs(self, field_names, extra_kwargs):
        # extra_kwargs contains 'default', 'required', 'validators=[UniqValidator]'
        # hidden_fields contains fields involved in constraints, but missing in serializer fields
        model = self.Meta.model

        uniq_extra_kwargs = {}

        hidden_fields = {}

        field_names = set(field_names)
        unique_fields = set()
        unique_together_fields = set()

        # include `unique_with` from model indexes
        # so long as all the field names are included on the serializer.
        uniq_indexes = filter(lambda i: i.get('unique', False), model._meta.get('index_specs', []))
        for idx in uniq_indexes:
            field_set = set(map(lambda e: e[0], idx['fields']))
            if field_names.issuperset(field_set):
                if len(field_set) == 1:
                    unique_fields |= field_set
                else:
                    unique_together_fields |= field_set

        for field_name in unique_fields:
            uniq_extra_kwargs[field_name] = {
                'required': True,
                'validators': [UniqueValidator(queryset=model.objects)]
            }

        for field_name in unique_together_fields:
            fld = model._fields[field_name]
            if has_default(fld):
                uniq_extra_kwargs[field_name] = {'default': fld.default}
            else:
                uniq_extra_kwargs[field_name] = {'required': True}

        # Update `extra_kwargs` with any new options.
        for key, value in uniq_extra_kwargs.items():
            if key in extra_kwargs:
                if key == 'validators' and key in extra_kwargs:
                    extra_kwargs[key].append(value)
                extra_kwargs[key].update(value)
            else:
                extra_kwargs[key] = value

        return extra_kwargs, hidden_fields

    def get_unique_together_validators(self):
        model = self.Meta.model
        validators = []
        field_names = set(self.get_field_names(self._declared_fields, self.field_info))

        uniq_indexes = filter(lambda i: i.get('unique', False), model._meta.get('index_specs', []))
        for idx in uniq_indexes:
            if not idx.get('unique', False):
                continue
            field_set = tuple(map(lambda e: e[0], idx['fields']))
            if len(field_set) > 1 and field_names.issuperset(set(field_set)):
                validators.append(UniqueTogetherValidator(
                    queryset=model.objects,
                    fields=field_set
                ))
        return validators

    def get_unique_for_date_validators(self):
        # not supported in mongo
        return []

    def __repr__(self):
        return serializer_repr(self, indent=1)


class EmbeddedDocumentSerializer(DocumentSerializer):
    """ Serializer for EmbeddedDocuments.

    Skips id field and uniqueness validation.
    When saving, skips calling instance.save
    """
    _saving_instances = False

    def get_default_field_names(self, declared_fields, model_info):
        # skip id field
        return (
            list(declared_fields.keys()) +
            list(model_info.fields.keys()) +
            list(model_info.references.keys()) +
            list(model_info.embedded.keys())
        )

    def get_unique_together_validators(self):
        # skip the valaidators
        return []


class DynamicDocumentSerializer(DocumentSerializer):
    """ Serializer for DynamicDocuments.

    Maps all undefined fields to :class:`fields.DynamicField`.
    """
    def to_internal_value(self, data):
        """
        Updates _validated_data with dynamic data, i.e. data,
        not listed in fields.
        """
        ret = super(DynamicDocumentSerializer, self).to_internal_value(data)
        dynamic_data = self._get_dynamic_data(ret)
        ret.update(dynamic_data)
        return ret

    def _get_dynamic_data(self, validated_data):
        """
        Returns dict of data, not declared in serializer fields.
        Should be called after self.is_valid().
        """
        result = {}

        for key in self.initial_data:
            if key not in validated_data:
                try:
                    field = self.fields[key]
                    # no exception? this is either SkipField or error
                    # in particular, this might be a read-only field
                    # that was mistakingly given a value
                    if not isinstance(field, drf_fields.SkipField):
                        msg = (
                            'Field %s is missing from validated data,'
                            'but is not a SkipField!'
                        ) % key
                        raise AssertionError(msg)
                except KeyError:  # ok, this is dynamic data
                    result[key] = self.initial_data[key]
        return result

    def to_representation(self, instance):
        ret = super(DynamicDocumentSerializer, self).to_representation(instance)

        for field_name, field in self._map_dynamic_fields(instance).items():
            ret[field_name] = field.to_representation(field.get_attribute(instance))

        return ret

    def _map_dynamic_fields(self, document):
        dynamic_fields = {}
        if document._dynamic:
            for name, field in document._dynamic_fields.items():
                dfield = drfm_fields.DynamicField(model_field=field, required=False)
                dfield.bind(name, self)
                dynamic_fields[name] = dfield
        return dynamic_fields
